﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MainPage.xaml.cs" company="Microsoft">
//   Microsoft
// </copyright>
// <summary>
//   Defines the MainPage type.
// </summary>
// Copyright (c) Microsoft Corporation. All rights reserved.
// 
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
// EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
// OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
// ----------------------------------------------------------------------------------
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.

// ATTENTION - THIS FILE CONTAINS THIRD PARTY OPEN SOURCE CODE: 3D Tag Cloud Widget for BlogEngine.NET
// IT IS CLEARED ONLY FOR LIMITED USE BY Doan Huynh FOR THE My Semantic Search PRODUCT.
// DO NOT USE OR SHARE THIS CODE WITHOUT APPROVAL PURSUANT TO THE MICROSOFT OPEN SOURCE SOFTWARE APPROVAL POLICY
// THE SOURCE CODE FOR 3D Tag Cloud Widget for BlogEngine.NET HAS BEEN MODIFIED
// --------------------------------------------------------------------------------------------------------------------

namespace MySemanticSearch
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Controls.Primitives;
    using System.Windows.Input;
    using System.Windows.Media;

    using Kit3D.Windows.Media.Media3D;

    using MySemanticSearch.TagCloud;
    using MySemanticSearch.View;
    using MySemanticSearch.ViewModel;
    using MySemanticSearch.Web.Model;

    /// <summary>
    /// Code-behind main Silverlight page
    /// </summary>
    public partial class MainPage : IView
    {
        #region Private fields

        /// <summary>
        /// Amount of slow down for the tag cloud
        /// </summary>
        private const double SlowDownValue = 100.0;

        /// <summary>
        /// Rotational speed for the tag cloud
        /// </summary>
        private const double Speed = -2.5;

        /// <summary>
        /// Manages 3D transformation for the tag cloud
        /// </summary>
        private RotateTransform3D rotateTransform;

        /// <summary>
        /// Indicates whether the rotation runs
        /// </summary>
        private bool runRotation;

        /// <summary>
        /// Indicates the slowdown when rotating
        /// </summary>
        private double slowDownCounter;

        /// <summary>
        /// Manages the tags internally for the tag cloud
        /// </summary>
        private List<KeyPhraseCloud> keyPhraseBlocks;

        #endregion

        /// <summary>
        /// Initializes a new instance of the MainPage class
        /// </summary>
        public MainPage()
        {
            // Required to initialize variables
            InitializeComponent();

            var mainViewModel = new MainPageViewModel(this);
            this.DataContext = mainViewModel;
            this.Loaded += this.KeyPhraseCloudLoaded;
            this.Loaded += mainViewModel.MainViewLoaded;
            this.SearchTreeView.KeyDown += this.UpdateSearchBindings;
            this.SearchTreeView.KeyDown += mainViewModel.SearchButtonShortCutPressed;
            this.RelatedDocumentGrid.GotFocus += mainViewModel.RelatedDocumentGridGotFocus;
            this.RelatedDocumentGrid.LostFocus += mainViewModel.RelatedDocumentGridLostFocus;
        }

        /// <summary>
        /// This method updates the view to show the change of the selection
        /// </summary>
        /// <param name="selectedItem">
        /// The selected item.
        /// </param>
        public void UpdateDocumentListFocus(Document selectedItem)
        {
            if (selectedItem != null)
            {
                this.DocumentsDataGrid.Dispatcher.BeginInvoke(
                    delegate
                    {
                        this.UpdateLayout();
                        this.DocumentsDataGrid.SelectedItem = selectedItem;
                        this.DocumentsDataGrid.UpdateLayout();
                        this.DocumentsDataGrid.Focus();
                        this.DocumentsDataGrid.ScrollIntoView(selectedItem, DocumentsDataGrid.Columns.First());
                    });
            }
        }

        #region Public Methods

        /// <summary>
        /// Redraws the Tag Cloud
        /// </summary>
        /// <param name="tags">
        /// The tags param.
        /// </param>
        public void RedrawTagCloud(ObservableCollection<KeyPhrase> tags)
        {
            this.FillKeyPhrases(tags);
            this.SetupRotation();
        }
        
        #endregion

        #region TagCloud functions

        /// <summary>
        /// Configures the tag cloud canvas
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The event argument.
        /// </param>
        private void KeyPhraseCloudLoaded(object sender, RoutedEventArgs e)
        {
            CompositionTarget.Rendering += this.CompositionTargetRendering;
            this.CanvasTagCloud.MouseEnter += this.CanvasTagCloudMouseEnter;
            this.CanvasTagCloud.MouseLeave += this.CanvasTagCloudMouseLeave;

            this.FillKeyPhrases(new ObservableCollection<KeyPhrase>());
            this.SetupRotation();
        }

        /// <summary>
        /// Reload the tag cloud canvas
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The event argument.
        /// </param>
        private void SetupCloudRotation(object sender, RoutedEventArgs e)
        {
            this.SetupRotation();
        }
        
        /// <summary>
        /// Configures the rotation elements for the tag cloud
        /// </summary>
        private void SetupRotation()
        {
            this.runRotation = false;
            this.slowDownCounter = SlowDownValue;

            const double Sizex = 200.0;
            const double Sizey = 200.0;

            var rotateTransform3D = new RotateTransform3D
            {
                Rotation = new AxisAngleRotation3D(new Vector3D(1.0, 0.0, 0.0), 0.0)
            };
            this.rotateTransform = rotateTransform3D;
            var angle = Math.Sqrt(Math.Pow(Sizex, 2.0) + Math.Pow(Sizey, 2.0)) / 170.0;
            this.rotateTransform.Rotation = new AxisAngleRotation3D(new Vector3D(Sizey, Sizex, 0.0), angle);
        }

        /// <summary>
        /// Builds tags for the tag cloud
        /// </summary>
        /// <param name="tags">
        /// The tags param.
        /// </param>
        private void FillKeyPhrases(IList<KeyPhrase> tags)
        {
            this.keyPhraseBlocks = new List<KeyPhraseCloud>();
            var radius = this.CanvasTagCloud.Width / 3.0;
            var count = tags.Count;
            KeyPhrase keyphrase;
            KeyPhraseCloud item;

            var minScore = tags.Aggregate((float)1.0, (current, tag) => Math.Min(current, tag.Score));

            CanvasTagCloud.Children.Clear();

            for (var i = 1; i <= count; i++)
            {
                var p = Math.Acos(-1.0 + (((2.0 * i) - 1.0) / count));
                var t = Math.Sqrt(count * Math.PI) * p;
                var x = radius * Math.Cos(t) * Math.Sin(p);
                var coordinatey = radius * Math.Sin(t) * Math.Sin(p);
                var coordinatez = radius * Math.Cos(p);
                keyphrase = tags[i - 1];
                item = new KeyPhraseCloud(x, coordinatey, coordinatez, keyphrase.Name, tags[i - 1].Score, minScore);
                item.KeyPhraseButtonClickEvent += ((MainPageViewModel)this.DataContext).FilterDocumentsByTag;
                item.Redraw(this.CanvasTagCloud.Width / 2.0, this.CanvasTagCloud.Height / 2.0);
                CanvasTagCloud.Children.Add(item.KeyPhraseButton);
                this.keyPhraseBlocks.Add(item);
            }
        }

        /// <summary>
        /// Controls tag cloud rendering on the canvas
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The event argument.
        /// </param>
        private void CompositionTargetRendering(object sender, EventArgs e)
        {
            if (!(this.runRotation || (this.slowDownCounter <= 0.0)))
            {
                var rotation = (AxisAngleRotation3D)this.rotateTransform.Rotation;
                rotation.Angle *= this.slowDownCounter / SlowDownValue;
                this.rotateTransform.Rotation = rotation;
                this.slowDownCounter--;
            }

            if (((AxisAngleRotation3D)this.rotateTransform.Rotation).Angle > 0.05)
            {
                this.RotateBlocks();
            }
        }

        /// <summary>
        /// Rotates tags within the tag cloud
        /// </summary>
        private void RotateBlocks()
        {
            foreach (var keyphrase in this.keyPhraseBlocks)
            {
                keyphrase.MiddlePoint = this.rotateTransform.Value.Transform(keyphrase.MiddlePoint);
                keyphrase.Redraw(CanvasTagCloud.ActualWidth / 2.0, CanvasTagCloud.ActualHeight / 2.0);
            }
        }

        /// <summary>
        /// Canvas event that controls when the mouse enters the tag cloud
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The event argument.
        /// </param>
        private void CanvasTagCloudMouseEnter(object sender, MouseEventArgs e)
        {
            CanvasTagCloud.MouseMove += this.CanvasTagCloudMouseMove;
            this.runRotation = true;
            this.slowDownCounter = SlowDownValue;
        }

        /// <summary>
        /// Canvas event that controls when the mouse moves over the tag cloud
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The event argument.
        /// </param>
        private void CanvasTagCloudMouseMove(object sender, MouseEventArgs e)
        {
            var position = e.GetPosition(CanvasTagCloud);
            var offsetX = position.X - (CanvasTagCloud.ActualWidth / 2.0);
            offsetX *= Speed;
            var offsetY = position.Y - (this.CanvasTagCloud.ActualHeight / 2.0);
            offsetY *= Speed;
            var angle = Math.Sqrt(Math.Pow(offsetX, 2.0) + Math.Pow(offsetY, 2.0)) / 170.0;
            this.rotateTransform.Rotation = new AxisAngleRotation3D(new Vector3D(offsetY, offsetX, 0.0), angle);
        }

        /// <summary>
        /// Canvas event that controls when the mouse exits the tag cloud
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The event arguments.
        /// </param>
        private void CanvasTagCloudMouseLeave(object sender, MouseEventArgs e)
        {
            CanvasTagCloud.MouseMove -= this.CanvasTagCloudMouseMove;
            this.runRotation = false;
            this.slowDownCounter = SlowDownValue;
        }

        #endregion

        /// <summary>
        /// Updates the bindings for searches
        /// </summary>
        /// <param name="sender">
        /// The sender.
        /// </param>
        /// <param name="e">
        /// The event arguments
        /// </param>
        private void UpdateSearchBindings(object sender, KeyEventArgs e)
        {
            // check for the specific 'enter' key, then check modifiers
            if (e.Key != Key.Enter)
            {
                return;
            }

            var binding = this.SearchTextBox.GetBindingExpression(TextBox.TextProperty);
            if (binding != null)
            {
                binding.UpdateSource();
            }

            binding = this.txtBoxSearchDocumentProperty.GetBindingExpression(TextBox.TextProperty);
            if (binding != null)
            {
                binding.UpdateSource();
            }

            binding = this.cmbSearchDocumentProperty.GetBindingExpression(Selector.SelectedItemProperty);

            if (binding != null)
            {
                binding.UpdateSource();
            }
        }
    }
}
